/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package calin.podcast;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import android.app.Activity;
import android.app.TabActivity;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.provider.MediaStore;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.Time;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.Window;
import android.widget.TabHost;
import android.widget.TabWidget;
import android.widget.TextView;
import android.widget.Toast;

public class MusicUtils {

	private static final String TAG = "MusicUtils";

	public interface Defs {
		public final static int OPEN_URL = 0;
		public final static int ADD_TO_PLAYLIST = 1;
		public final static int USE_AS_RINGTONE = 2;
		public final static int PLAYLIST_SELECTED = 3;
		public final static int NEW_PLAYLIST = 4;
		public final static int PLAY_SELECTION = 5;
		public final static int GOTO_START = 6;
		public final static int GOTO_PLAYBACK = 7;
		public final static int PARTY_SHUFFLE = 8;
		public final static int SHUFFLE_ALL = 9;
		public final static int DELETE_ITEM = 10;
		public final static int SCAN_DONE = 11;
		public final static int QUEUE = 12;
		public final static int CHILD_MENU_BASE = 13; // this should be the last
														// item
	}

	public static String makeAlbumsLabel(Context context, int numalbums,
			int numsongs, boolean isUnknown) {
		// There are two formats for the albums/songs information:
		// "N Song(s)" - used for unknown artist/album
		// "N Album(s)" - used for known albums

		StringBuilder songs_albums = new StringBuilder();

		Resources r = context.getResources();
		if (isUnknown) {
			if (numsongs == 1) {
				songs_albums.append(context.getString(R.string.onesong));
			} else {
				String f = r.getQuantityText(R.plurals.Nsongs, numsongs)
						.toString();
				sFormatBuilder.setLength(0);
				sFormatter.format(f, Integer.valueOf(numsongs));
				songs_albums.append(sFormatBuilder);
			}
		} else {
			String f = r.getQuantityText(R.plurals.Nalbums, numalbums)
					.toString();
			sFormatBuilder.setLength(0);
			sFormatter.format(f, Integer.valueOf(numalbums));
			songs_albums.append(sFormatBuilder);
			songs_albums.append(context.getString(R.string.albumsongseparator));
		}
		return songs_albums.toString();
	}

	/**
	 * This is now only used for the query screen
	 */
	public static String makeAlbumsSongsLabel(Context context, int numalbums,
			int numsongs, boolean isUnknown) {
		// There are several formats for the albums/songs information:
		// "1 Song" - used if there is only 1 song
		// "N Songs" - used for the "unknown artist" item
		// "1 Album"/"N Songs"
		// "N Album"/"M Songs"
		// Depending on locale, these may need to be further subdivided

		StringBuilder songs_albums = new StringBuilder();

		if (numsongs == 1) {
			songs_albums.append(context.getString(R.string.onesong));
		} else {
			Resources r = context.getResources();
			if (!isUnknown) {
				String f = r.getQuantityText(R.plurals.Nalbums, numalbums)
						.toString();
				sFormatBuilder.setLength(0);
				sFormatter.format(f, Integer.valueOf(numalbums));
				songs_albums.append(sFormatBuilder);
				songs_albums.append(context
						.getString(R.string.albumsongseparator));
			}
			String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString();
			sFormatBuilder.setLength(0);
			sFormatter.format(f, Integer.valueOf(numsongs));
			songs_albums.append(sFormatBuilder);
		}
		return songs_albums.toString();
	}

	public static IMediaPlaybackService sService = null;
	private static HashMap<Context, ServiceBinder> sConnectionMap = new HashMap<Context, ServiceBinder>();

	public static class ServiceToken {
		ContextWrapper mWrappedContext;

		ServiceToken(ContextWrapper context) {
			mWrappedContext = context;
		}
	}

	public static ServiceToken bindToService(Activity context) {
		return bindToService(context, null);
	}

	public static ServiceToken bindToService(Activity context,
			ServiceConnection callback) {

		Activity realActivity = context.getParent();
		if (realActivity == null) {
			realActivity = context;
		}
		Log.d("Test", realActivity.toString());
		try {
			ContextWrapper cw = new ContextWrapper(realActivity);
			cw.startService(new Intent(cw, MediaPlaybackService.class));
			ServiceBinder sb = new ServiceBinder(callback);
			if (cw.bindService(
					(new Intent()).setClass(cw, MediaPlaybackService.class),
					sb, 0)) {
				sConnectionMap.put(cw, sb);
				return new ServiceToken(cw);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		Log.e("Music", "Failed to bind to service");
		return null;
	}

	public static void unbindFromService(ServiceToken token) {
		if (token == null) {
			Log.e("MusicUtils", "Trying to unbind with null token");
			return;
		}
		ContextWrapper cw = token.mWrappedContext;
		ServiceBinder sb = sConnectionMap.remove(cw);
		if (sb == null) {
			Log.e("MusicUtils", "Trying to unbind for unknown Context");
			return;
		}
		cw.unbindService(sb);
		if (sConnectionMap.isEmpty()) {
			// presumably there is nobody interested in the service at this
			// point,
			// so don't hang on to the ServiceConnection
			sService = null;
		}
	}

	private static class ServiceBinder implements ServiceConnection {
		ServiceConnection mCallback;

		ServiceBinder(ServiceConnection callback) {
			mCallback = callback;
		}

		public void onServiceConnected(ComponentName className,
				android.os.IBinder service) {
			sService = IMediaPlaybackService.Stub.asInterface(service);
			initAlbumArtCache();
			if (mCallback != null) {
				mCallback.onServiceConnected(className, service);
			}
		}

		public void onServiceDisconnected(ComponentName className) {
			if (mCallback != null) {
				mCallback.onServiceDisconnected(className);
			}
			sService = null;
		}
	}

	public static long getCurrentAlbumId() {
		if (sService != null) {
			try {
				return sService.getAlbumId();
			} catch (RemoteException ex) {
			}
		}
		return -1;
	}

	public static long getCurrentArtistId() {
		if (MusicUtils.sService != null) {
			try {
				return sService.getArtistId();
			} catch (RemoteException ex) {
			}
		}
		return -1;
	}

	public static long getCurrentAudioId() {
		if (MusicUtils.sService != null) {
			try {
				return sService.getAudioId();
			} catch (RemoteException ex) {
			}
		}
		return -1;
	}

	public static int getCurrentShuffleMode() {
		int mode = MediaPlaybackService.SHUFFLE_NONE;
		if (sService != null) {
			try {
				mode = sService.getShuffleMode();
			} catch (RemoteException ex) {
			}
		}
		return mode;
	}

	public static void togglePartyShuffle() {
		if (sService != null) {
			int shuffle = getCurrentShuffleMode();
			try {
				if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
					sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
				} else {
					sService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
				}
			} catch (RemoteException ex) {
			}
		}
	}

	public static void setPartyShuffleMenuIcon(Menu menu) {
		MenuItem item = menu.findItem(Defs.PARTY_SHUFFLE);
		if (item != null) {
			int shuffle = MusicUtils.getCurrentShuffleMode();
			if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
				item.setIcon(R.drawable.ic_menu_party_shuffle);
				item.setTitle(R.string.party_shuffle_off);
			} else {
				item.setIcon(R.drawable.ic_menu_party_shuffle);
				item.setTitle(R.string.party_shuffle);
			}
		}
	}

	/*
	 * Returns true if a file is currently opened for playback (regardless of
	 * whether it's playing or paused).
	 */
	public static boolean isMusicLoaded() {
		if (MusicUtils.sService != null) {
			try {
				return sService.getPath() != null;
			} catch (RemoteException ex) {
			}
		}
		return false;
	}

	private final static long[] sEmptyList = new long[0];
	
	public static List<String> getSongListForPath(String Path){
		List<String> list=null;
		
		return list;
	}

	public static long[] getSongListForCursor(Cursor cursor) {
		if (cursor == null) {
			return sEmptyList;
		}
		int len = cursor.getCount();
		long[] list = new long[len];
		cursor.moveToFirst();
		int colidx = -1;
		try {
			colidx = cursor
					.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID);
		} catch (IllegalArgumentException ex) {
			colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
		}
		for (int i = 0; i < len; i++) {
			list[i] = cursor.getLong(colidx);
			cursor.moveToNext();
		}
		return list;
	}

	public static long[] getSongListForArtist(Context context, long id) {
		final String[] ccols = new String[] { MediaStore.Audio.Media._ID };
		String where = MediaStore.Audio.Media.ARTIST_ID + "=" + id + " AND "
				+ MediaStore.Audio.Media.IS_MUSIC + "=1";
		Cursor cursor = query(context,
				MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, ccols, where,
				null, MediaStore.Audio.Media.ALBUM_KEY + ","
						+ MediaStore.Audio.Media.TRACK);

		if (cursor != null) {
			long[] list = getSongListForCursor(cursor);
			cursor.close();
			return list;
		}
		return sEmptyList;
	}

	public static long[] getSongListForAlbum(Context context, long id) {
		final String[] ccols = new String[] { MediaStore.Audio.Media._ID };
		String where = MediaStore.Audio.Media.ALBUM_ID + "=" + id + " AND "
				+ MediaStore.Audio.Media.IS_MUSIC + "=1";
		Cursor cursor = query(context,
				MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, ccols, where,
				null, MediaStore.Audio.Media.TRACK);

		if (cursor != null) {
			long[] list = getSongListForCursor(cursor);
			cursor.close();
			return list;
		}
		return sEmptyList;
	}

	public static long[] getSongListForPlaylist(Context context, long plid) {
		final String[] ccols = new String[] { MediaStore.Audio.Playlists.Members.AUDIO_ID };
		Cursor cursor = query(context,
				MediaStore.Audio.Playlists.Members.getContentUri("external",
						plid), ccols, null, null,
				MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);

		if (cursor != null) {
			long[] list = getSongListForCursor(cursor);
			cursor.close();
			return list;
		}
		return sEmptyList;
	}

	public static void playPlaylist(Context context, long plid) {
		long[] list = getSongListForPlaylist(context, plid);
		if (list != null) {
			playAll(context, list, -1, false);
		}
	}

	public static long[] getAllSongs(Context context) {
		Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
				new String[] { MediaStore.Audio.Media._ID },
				MediaStore.Audio.Media.IS_MUSIC + "=1", null, null);
		try {
			if (c == null || c.getCount() == 0) {
				return null;
			}
			int len = c.getCount();
			long[] list = new long[len];
			for (int i = 0; i < len; i++) {
				c.moveToNext();
				list[i] = c.getLong(0);
			}

			return list;
		} finally {
			if (c != null) {
				c.close();
			}
		}
	}

	/**
	 * Fills out the given submenu with items for "new playlist" and any
	 * existing playlists. When the user selects an item, the application will
	 * receive PLAYLIST_SELECTED with the Uri of the selected playlist,
	 * NEW_PLAYLIST if a new playlist should be created, and QUEUE if the
	 * "current playlist" was selected.
	 * 
	 * @param context
	 *            The context to use for creating the menu items
	 * @param sub
	 *            The submenu to add the items to.
	 */
	public static void makePlaylistMenu(Context context, SubMenu sub) {
		String[] cols = new String[] { MediaStore.Audio.Playlists._ID,
				MediaStore.Audio.Playlists.NAME };
		ContentResolver resolver = context.getContentResolver();
		if (resolver == null) {
			System.out.println("resolver = null");
		} else {
			String whereclause = MediaStore.Audio.Playlists.NAME + " != ''";
			Cursor cur = resolver.query(
					MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, cols,
					whereclause, null, MediaStore.Audio.Playlists.NAME);
			sub.clear();
			sub.add(1, Defs.QUEUE, 0, R.string.queue);
			sub.add(1, Defs.NEW_PLAYLIST, 0, R.string.new_playlist);
			if (cur != null && cur.getCount() > 0) {
				// sub.addSeparator(1, 0);
				cur.moveToFirst();
				while (!cur.isAfterLast()) {
					Intent intent = new Intent();
					intent.putExtra("playlist", cur.getLong(0));
					// if (cur.getInt(0) == mLastPlaylistSelected) {
					// sub.add(0, MusicBaseActivity.PLAYLIST_SELECTED,
					// cur.getString(1)).setIntent(intent);
					// } else {
					sub.add(1, Defs.PLAYLIST_SELECTED, 0, cur.getString(1))
							.setIntent(intent);
					// }
					cur.moveToNext();
				}
			}
			if (cur != null) {
				cur.close();
			}
		}
	}

	public static void clearPlaylist(Context context, int plid) {

		Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
				plid);
		context.getContentResolver().delete(uri, null, null);
		return;
	}

	public static void deleteTracks(Context context, long[] list) {

		String[] cols = new String[] { MediaStore.Audio.Media._ID,
				MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ALBUM_ID };
		StringBuilder where = new StringBuilder();
		where.append(MediaStore.Audio.Media._ID + " IN (");
		for (int i = 0; i < list.length; i++) {
			where.append(list[i]);
			if (i < list.length - 1) {
				where.append(",");
			}
		}
		where.append(")");
		Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
				cols, where.toString(), null, null);

		if (c != null) {

			// step 1: remove selected tracks from the current playlist, as well
			// as from the album art cache
			try {
				c.moveToFirst();
				while (!c.isAfterLast()) {
					// remove from current playlist
					long id = c.getLong(0);
					sService.removeTrack(id);
					// remove from album art cache
					long artIndex = c.getLong(2);
					synchronized (sArtCache) {
						sArtCache.remove(artIndex);
					}
					c.moveToNext();
				}
			} catch (RemoteException ex) {
			}

			// step 2: remove selected tracks from the database
			context.getContentResolver().delete(
					MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
					where.toString(), null);

			// step 3: remove files from card
			c.moveToFirst();
			while (!c.isAfterLast()) {
				String name = c.getString(1);
				File f = new File(name);
				try { // File.delete can throw a security exception
					if (!f.delete()) {
						// I'm not sure if we'd ever get here (deletion would
						// have to fail, but no exception thrown)
						Log.e("MusicUtils", "Failed to delete file " + name);
					}
					c.moveToNext();
				} catch (SecurityException ex) {
					c.moveToNext();
				}
			}
			c.close();
		}

		String message = context.getResources().getQuantityString(
				R.plurals.NNNtracksdeleted, list.length,
				Integer.valueOf(list.length));

		Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
		// We deleted a number of tracks, which could affect any number of
		// things
		// in the media content domain, so update everything.
		context.getContentResolver().notifyChange(Uri.parse("content://media"),
				null);
	}

	public static void addToCurrentPlaylist(Context context, long[] list) {
		if (sService == null) {
			return;
		}
		try {
			sService.enqueue(list, MediaPlaybackService.LAST);
			String message = context.getResources().getQuantityString(
					R.plurals.NNNtrackstoplaylist, list.length,
					Integer.valueOf(list.length));
			Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
		} catch (RemoteException ex) {
		}
	}

	private static ContentValues[] sContentValuesCache = null;

	/**
	 * @param ids
	 *            The source array containing all the ids to be added to the
	 *            playlist
	 * @param offset
	 *            Where in the 'ids' array we start reading
	 * @param len
	 *            How many items to copy during this pass
	 * @param base
	 *            The play order offset to use for this pass
	 */
	private static void makeInsertItems(long[] ids, int offset, int len,
			int base) {
		// adjust 'len' if would extend beyond the end of the source array
		if (offset + len > ids.length) {
			len = ids.length - offset;
		}
		// allocate the ContentValues array, or reallocate if it is the wrong
		// size
		if (sContentValuesCache == null || sContentValuesCache.length != len) {
			sContentValuesCache = new ContentValues[len];
		}
		// fill in the ContentValues array with the right values for this pass
		for (int i = 0; i < len; i++) {
			if (sContentValuesCache[i] == null) {
				sContentValuesCache[i] = new ContentValues();
			}

			sContentValuesCache[i].put(
					MediaStore.Audio.Playlists.Members.PLAY_ORDER, base
							+ offset + i);
			sContentValuesCache[i].put(
					MediaStore.Audio.Playlists.Members.AUDIO_ID,
					ids[offset + i]);
		}
	}

	public static void addToPlaylist(Context context, long[] ids,
			long playlistid) {
		if (ids == null) {
			// this shouldn't happen (the menuitems shouldn't be visible
			// unless the selected item represents something playable
			Log.e("MusicBase", "ListSelection null");
		} else {
			int size = ids.length;
			ContentResolver resolver = context.getContentResolver();
			// need to determine the number of items currently in the playlist,
			// so the play_order field can be maintained.
			String[] cols = new String[] { "count(*)" };
			Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(
					"external", playlistid);
			Cursor cur = resolver.query(uri, cols, null, null, null);
			cur.moveToFirst();
			int base = cur.getInt(0);
			cur.close();
			int numinserted = 0;
			for (int i = 0; i < size; i += 1000) {
				makeInsertItems(ids, i, 1000, base);
				numinserted += resolver.bulkInsert(uri, sContentValuesCache);
			}
			String message = context.getResources().getQuantityString(
					R.plurals.NNNtrackstoplaylist, numinserted, numinserted);
			Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
			// mLastPlaylistSelected = playlistid;
		}
	}

	public static Cursor query(Context context, Uri uri, String[] projection,
			String selection, String[] selectionArgs, String sortOrder,
			int limit) {
		try {
			ContentResolver resolver = context.getContentResolver();
			if (resolver == null) {
				return null;
			}
			if (limit > 0) {
				uri = uri.buildUpon().appendQueryParameter("limit", "" + limit)
						.build();
			}
			return resolver.query(uri, projection, selection, selectionArgs,
					sortOrder);
		} catch (UnsupportedOperationException ex) {
			return null;
		}

	}

	public static Cursor query(Context context, Uri uri, String[] projection,
			String selection, String[] selectionArgs, String sortOrder) {
		return query(context, uri, projection, selection, selectionArgs,
				sortOrder, 0);
	}

	public static boolean isMediaScannerScanning(Context context) {
		boolean result = false;
		Cursor cursor = query(context, MediaStore.getMediaScannerUri(),
				new String[] { MediaStore.MEDIA_SCANNER_VOLUME }, null, null,
				null);
		if (cursor != null) {
			if (cursor.getCount() == 1) {
				cursor.moveToFirst();
				result = "external".equals(cursor.getString(0));
			}
			cursor.close();
		}

		return result;
	}

	public static void setSpinnerState(Activity a) {
		if (isMediaScannerScanning(a)) {
			// start the progress spinner
			a.getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
					Window.PROGRESS_INDETERMINATE_ON);

			a.getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
					Window.PROGRESS_VISIBILITY_ON);
		} else {
			// stop the progress spinner
			a.getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
					Window.PROGRESS_VISIBILITY_OFF);
		}
	}

	private static String mLastSdStatus;

	public static void displayDatabaseError(Activity a) {
		if (a.isFinishing()) {
			// When switching tabs really fast, we can end up with a null
			// cursor (not sure why), which will bring us here.
			// Don't bother showing an error message in that case.
			return;
		}

		String status = Environment.getExternalStorageState();
		int title = R.string.sdcard_error_title;
		int message = R.string.sdcard_error_message;

		if (status.equals(Environment.MEDIA_SHARED)
				|| status.equals(Environment.MEDIA_UNMOUNTED)) {
			title = R.string.sdcard_busy_title;
			message = R.string.sdcard_busy_message;
		} else if (status.equals(Environment.MEDIA_REMOVED)) {
			title = R.string.sdcard_missing_title;
			message = R.string.sdcard_missing_message;
		} else if (status.equals(Environment.MEDIA_MOUNTED)) {
			// The card is mounted, but we didn't get a valid cursor.
			// This probably means the mediascanner hasn't started scanning the
			// card yet (there is a small window of time during boot where this
			// will happen).
			a.setTitle("");
			Intent intent = new Intent();
			intent.setClass(a, ScanningProgress.class);
			a.startActivityForResult(intent, Defs.SCAN_DONE);
		} else if (!TextUtils.equals(mLastSdStatus, status)) {
			mLastSdStatus = status;
			Log.d(TAG, "sd card: " + status);
		}

		a.setTitle(title);
		View v = a.findViewById(R.id.sd_message);
		if (v != null) {
			v.setVisibility(View.VISIBLE);
		}
		v = a.findViewById(R.id.sd_icon);
		if (v != null) {
			v.setVisibility(View.VISIBLE);
		}
		v = a.findViewById(android.R.id.list);
		if (v != null) {
			v.setVisibility(View.GONE);
		}
		v = a.findViewById(R.id.buttonbar);
		if (v != null) {
			v.setVisibility(View.GONE);
		}
		TextView tv = (TextView) a.findViewById(R.id.sd_message);
		tv.setText(message);
	}

	public static void hideDatabaseError(Activity a) {
		View v = a.findViewById(R.id.sd_message);
		if (v != null) {
			v.setVisibility(View.GONE);
		}
		v = a.findViewById(R.id.sd_icon);
		if (v != null) {
			v.setVisibility(View.GONE);
		}
		v = a.findViewById(android.R.id.list);
		if (v != null) {
			v.setVisibility(View.VISIBLE);
		}
	}

	static protected Uri getContentURIForPath(String path) {
		return Uri.fromFile(new File(path));
	}

	/*
	 * Try to use String.format() as little as possible, because it creates a
	 * new Formatter every time you call it, which is very inefficient. Reusing
	 * an existing Formatter more than tripled the speed of makeTimeString().
	 * This Formatter/StringBuilder are also used by makeAlbumSongsLabel()
	 */
	private static StringBuilder sFormatBuilder = new StringBuilder();
	private static Formatter sFormatter = new Formatter(sFormatBuilder,
			Locale.getDefault());
	private static final Object[] sTimeArgs = new Object[5];

	public static String makeTimeString(Context context, long secs) {
		String durationformat = context
				.getString(secs < 3600 ? R.string.durationformatshort
						: R.string.durationformatlong);

		/*
		 * Provide multiple arguments so the format can be changed easily by
		 * modifying the xml.
		 */
		sFormatBuilder.setLength(0);

		final Object[] timeArgs = sTimeArgs;
		timeArgs[0] = secs / 3600;
		timeArgs[1] = secs / 60;
		timeArgs[2] = (secs / 60) % 60;
		timeArgs[3] = secs;
		timeArgs[4] = secs % 60;

		return sFormatter.format(durationformat, timeArgs).toString();
	}

	public static void shuffleAll(Context context, Cursor cursor) {
		playAll(context, cursor, 0, true);
	}

	public static void playAll(Context context, Cursor cursor) {
		playAll(context, cursor, 0, false);
	}

	public static void playAll(Context context, String Path) {
		playAll(context, Path, 0, false);
	}

	public static void playAll(Context context, Cursor cursor, int position) {
		playAll(context, cursor, position, false);
	}

	public static void playAll(Context context, long[] list, int position) {
		playAll(context, list, position, false);
	}

	private static void playAll(Context context, String Path, int position,
			boolean force_shuffle) {

		List<String> list = getSongListForPath(Path);
		playAll(context, list, position, force_shuffle);
	}

	private static void playAll(Context context, Cursor cursor, int position,
			boolean force_shuffle) {

		long[] list = getSongListForCursor(cursor);
		playAll(context, list, position, force_shuffle);
	}

	public static void playAll(Context context, List<String> list, int position,
			boolean force_shuffle) {
		if (list.isEmpty() || sService == null) {
			Log.d("MusicUtils", "attempt to play empty song list");
			// Don't try to play empty playlists. Nothing good will come of it.
			String message = context.getString(R.string.emptyplaylist,
					list.isEmpty());
			Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
			return;
		}
		try {
			if (force_shuffle) {
				sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
			}
//			long curid = sService.getAudioId();
//			int curpos = sService.getQueuePosition();
//			if (position != -1 && curpos == position && curid == list[position]) {
//				// The selected file is the file that's currently playing;
//				// figure out if we need to restart with a new playlist,
//				// or just launch the playback activity.
//				long[] playlist = sService.getQueue();
//				if (Arrays.equals(list, playlist)) {
//					// we don't need to set a new list, but we should resume
//					// playback if needed
//					sService.play();
//					return; // the 'finally' block will still run
//				}
//			}
			if (position < 0) {
				position = 0;
			}
			sService.openPathList(list, position);
		} catch (RemoteException ex) {
		} finally {
			Intent intent = new Intent("calin.podcast.PLAYBACK_VIEWER")
					.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
			context.startActivity(intent);
		}
	}

	private static void playAll(Context context, long[] list, int position,
			boolean force_shuffle) {
		if (list.length == 0 || sService == null) {
			Log.d("MusicUtils", "attempt to play empty song list");
			// Don't try to play empty playlists. Nothing good will come of it.
			String message = context.getString(R.string.emptyplaylist,
					list.length);
			Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
			return;
		}
		try {
			if (force_shuffle) {
				sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
			}
			long curid = sService.getAudioId();
			int curpos = sService.getQueuePosition();
			if (position != -1 && curpos == position && curid == list[position]) {
				// The selected file is the file that's currently playing;
				// figure out if we need to restart with a new playlist,
				// or just launch the playback activity.
				long[] playlist = sService.getQueue();
				if (Arrays.equals(list, playlist)) {
					// we don't need to set a new list, but we should resume
					// playback if needed
					sService.play();
					return; // the 'finally' block will still run
				}
			}
			if (position < 0) {
				position = 0;
			}
			sService.open(list, force_shuffle ? -1 : position);
			sService.play();
		} catch (RemoteException ex) {
		} finally {
			Intent intent = new Intent("calin.podcast.PLAYBACK_VIEWER")
					.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
			context.startActivity(intent);
		}
	}

	public static void clearQueue() {
		try {
			sService.removeTracks(0, Integer.MAX_VALUE);
		} catch (RemoteException ex) {
		}
	}

	// A really simple BitmapDrawable-like class, that doesn't do
	// scaling, dithering or filtering.
	private static class FastBitmapDrawable extends Drawable {
		private Bitmap mBitmap;

		public FastBitmapDrawable(Bitmap b) {
			mBitmap = b;
		}

		@Override
		public void draw(Canvas canvas) {
			canvas.drawBitmap(mBitmap, 0, 0, null);
		}

		@Override
		public int getOpacity() {
			return PixelFormat.OPAQUE;
		}

		@Override
		public void setAlpha(int alpha) {
		}

		@Override
		public void setColorFilter(ColorFilter cf) {
		}
	}

	private static int sArtId = -2;
	private static Bitmap mCachedBit = null;
	private static final BitmapFactory.Options sBitmapOptionsCache = new BitmapFactory.Options();
	private static final BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options();
	private static final Uri sArtworkUri = Uri
			.parse("content://media/external/audio/albumart");
	private static final HashMap<Long, Drawable> sArtCache = new HashMap<Long, Drawable>();
	private static int sArtCacheId = -1;

	static {
		// for the cache,
		// 565 is faster to decode and display
		// and we don't want to dither here because the image will be scaled
		// down later
		sBitmapOptionsCache.inPreferredConfig = Bitmap.Config.RGB_565;
		sBitmapOptionsCache.inDither = false;

		sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
		sBitmapOptions.inDither = false;
	}

	public static void initAlbumArtCache() {
		try {
			int id = sService.getMediaMountedCount();
			if (id != sArtCacheId) {
				clearAlbumArtCache();
				sArtCacheId = id;
			}
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}

	public static void clearAlbumArtCache() {
		synchronized (sArtCache) {
			sArtCache.clear();
		}
	}

	public static Drawable getCachedArtwork(Context context, long artIndex,
			BitmapDrawable defaultArtwork) {
		Drawable d = null;
		synchronized (sArtCache) {
			d = sArtCache.get(artIndex);
		}
		if (d == null) {
			d = defaultArtwork;
			final Bitmap icon = defaultArtwork.getBitmap();
			int w = icon.getWidth();
			int h = icon.getHeight();
			Bitmap b = MusicUtils.getArtworkQuick(context, artIndex, w, h);
			if (b != null) {
				d = new FastBitmapDrawable(b);
				synchronized (sArtCache) {
					// the cache may have changed since we checked
					Drawable value = sArtCache.get(artIndex);
					if (value == null) {
						sArtCache.put(artIndex, d);
					} else {
						d = value;
					}
				}
			}
		}
		return d;
	}

	// Get album art for specified album. This method will not try to
	// fall back to getting artwork directly from the file, nor will
	// it attempt to repair the database.
	private static Bitmap getArtworkQuick(Context context, long album_id,
			int w, int h) {
		// NOTE: There is in fact a 1 pixel border on the right side in the
		// ImageView
		// used to display this drawable. Take it into account now, so we don't
		// have to
		// scale later.
		w -= 1;
		ContentResolver res = context.getContentResolver();
		Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
		if (uri != null) {
			ParcelFileDescriptor fd = null;
			try {
				fd = res.openFileDescriptor(uri, "r");
				int sampleSize = 1;

				// Compute the closest power-of-two scale factor
				// and pass that to sBitmapOptionsCache.inSampleSize, which will
				// result in faster decoding and better quality
				sBitmapOptionsCache.inJustDecodeBounds = true;
				BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(),
						null, sBitmapOptionsCache);
				int nextWidth = sBitmapOptionsCache.outWidth >> 1;
				int nextHeight = sBitmapOptionsCache.outHeight >> 1;
				while (nextWidth > w && nextHeight > h) {
					sampleSize <<= 1;
					nextWidth >>= 1;
					nextHeight >>= 1;
				}

				sBitmapOptionsCache.inSampleSize = sampleSize;
				sBitmapOptionsCache.inJustDecodeBounds = false;
				Bitmap b = BitmapFactory.decodeFileDescriptor(
						fd.getFileDescriptor(), null, sBitmapOptionsCache);

				if (b != null) {
					// finally rescale to exactly the size we need
					if (sBitmapOptionsCache.outWidth != w
							|| sBitmapOptionsCache.outHeight != h) {
						Bitmap tmp = Bitmap.createScaledBitmap(b, w, h, true);
						// Bitmap.createScaledBitmap() can return the same
						// bitmap
						if (tmp != b)
							b.recycle();
						b = tmp;
					}
				}

				return b;
			} catch (FileNotFoundException e) {
			} finally {
				try {
					if (fd != null)
						fd.close();
				} catch (IOException e) {
				}
			}
		}
		return null;
	}

	/**
	 * Get album art for specified album. You should not pass in the album id
	 * for the "unknown" album here (use -1 instead) This method always returns
	 * the default album art icon when no album art is found.
	 */
	public static Bitmap getArtwork(Context context, long song_id, long album_id) {
		return getArtwork(context, song_id, album_id, true);
	}

	/**
	 * Get album art for specified album. You should not pass in the album id
	 * for the "unknown" album here (use -1 instead)
	 */
	public static Bitmap getArtwork(Context context, long song_id,
			long album_id, boolean allowdefault) {

		if (album_id < 0) {
			// This is something that is not in the database, so get the album
			// art directly
			// from the file.
			if (song_id >= 0) {
				Bitmap bm = getArtworkFromFile(context, song_id, -1);
				if (bm != null) {
					return bm;
				}
			}
			if (allowdefault) {
				return getDefaultArtwork(context);
			}
			return null;
		}

		ContentResolver res = context.getContentResolver();
		Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
		if (uri != null) {
			InputStream in = null;
			try {
				in = res.openInputStream(uri);
				return BitmapFactory.decodeStream(in, null, sBitmapOptions);
			} catch (FileNotFoundException ex) {
				// The album art thumbnail does not actually exist. Maybe the
				// user deleted it, or
				// maybe it never existed to begin with.
				Bitmap bm = getArtworkFromFile(context, song_id, album_id);
				if (bm != null) {
					if (bm.getConfig() == null) {
						bm = bm.copy(Bitmap.Config.RGB_565, false);
						if (bm == null && allowdefault) {
							return getDefaultArtwork(context);
						}
					}
				} else if (allowdefault) {
					bm = getDefaultArtwork(context);
				}
				return bm;
			} finally {
				try {
					if (in != null) {
						in.close();
					}
				} catch (IOException ex) {
				}
			}
		}

		return null;
	}

	// get album art for specified file
	private static final String sExternalMediaUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
			.toString();

	private static Bitmap getArtworkFromFile(Context context, long songid,
			long albumid) {
		Bitmap bm = null;
		byte[] art = null;
		String path = null;

		if (albumid < 0 && songid < 0) {
			throw new IllegalArgumentException(
					"Must specify an album or a song id");
		}

		try {
			if (albumid < 0) {
				Uri uri = Uri.parse("content://media/external/audio/media/"
						+ songid + "/albumart");
				ParcelFileDescriptor pfd = context.getContentResolver()
						.openFileDescriptor(uri, "r");
				if (pfd != null) {
					FileDescriptor fd = pfd.getFileDescriptor();
					bm = BitmapFactory.decodeFileDescriptor(fd);
				}
			} else {
				Uri uri = ContentUris.withAppendedId(sArtworkUri, albumid);
				ParcelFileDescriptor pfd = context.getContentResolver()
						.openFileDescriptor(uri, "r");
				if (pfd != null) {
					FileDescriptor fd = pfd.getFileDescriptor();
					bm = BitmapFactory.decodeFileDescriptor(fd);
				}
			}
		} catch (FileNotFoundException ex) {
			//
		}
		if (bm != null) {
			mCachedBit = bm;
		}
		return bm;
	}

	private static Bitmap getDefaultArtwork(Context context) {
		BitmapFactory.Options opts = new BitmapFactory.Options();
		opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
		return BitmapFactory.decodeStream(context.getResources()
				.openRawResource(R.drawable.albumart_mp_unknown), null, opts);
	}

	static int getIntPref(Context context, String name, int def) {
		SharedPreferences prefs = context.getSharedPreferences(
				context.getPackageName(), Context.MODE_PRIVATE);
		return prefs.getInt(name, def);
	}

	static void setIntPref(Context context, String name, int value) {
		SharedPreferences prefs = context.getSharedPreferences(
				context.getPackageName(), Context.MODE_PRIVATE);
		Editor ed = prefs.edit();
		ed.putInt(name, value);
		ed.commit();
	}

	static void setRingtone(Context context, long id) {
		ContentResolver resolver = context.getContentResolver();
		// Set the flag in the database to mark this as a ringtone
		Uri ringUri = ContentUris.withAppendedId(
				MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
		try {
			ContentValues values = new ContentValues(2);
			values.put(MediaStore.Audio.Media.IS_RINGTONE, "1");
			values.put(MediaStore.Audio.Media.IS_ALARM, "1");
			resolver.update(ringUri, values, null, null);
		} catch (UnsupportedOperationException ex) {
			// most likely the card just got unmounted
			Log.e(TAG, "couldn't set ringtone flag for id " + id);
			return;
		}

		String[] cols = new String[] { MediaStore.Audio.Media._ID,
				MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.TITLE };

		String where = MediaStore.Audio.Media._ID + "=" + id;
		Cursor cursor = query(context,
				MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cols, where, null,
				null);
		try {
			if (cursor != null && cursor.getCount() == 1) {
				// Set the system setting to make this the current ringtone
				cursor.moveToFirst();
				Settings.System.putString(resolver, Settings.System.RINGTONE,
						ringUri.toString());
				String message = context.getString(R.string.ringtone_set,
						cursor.getString(2));
				Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
			}
		} finally {
			if (cursor != null) {
				cursor.close();
			}
		}
	}

	static int sActiveTabIndex = -1;

	static boolean updateButtonBar(Activity a, int highlight) {
		final TabWidget ll = (TabWidget) a.findViewById(R.id.buttonbar);
		boolean withtabs = false;
		Intent intent = a.getIntent();
		if (intent != null) {
			withtabs = intent.getBooleanExtra("withtabs", false);
		}

		try{
		if (highlight == 0 || !withtabs) {
			ll.setVisibility(View.GONE);
			return withtabs;
		} else if (withtabs) {
			ll.setVisibility(View.VISIBLE);
		}
		}catch(Exception e){
			e.printStackTrace();
		}
		for (int i = ll.getChildCount() - 1; i >= 0; i--) {

			View v = ll.getChildAt(i);
			boolean isActive = (v.getId() == highlight);
			if (isActive) {
				ll.setCurrentTab(i);
				sActiveTabIndex = i;
			}
			v.setTag(i);
			v.setOnFocusChangeListener(new View.OnFocusChangeListener() {

				public void onFocusChange(View v, boolean hasFocus) {
					if (hasFocus) {
						for (int i = 0; i < ll.getTabCount(); i++) {
							if (ll.getChildTabViewAt(i) == v) {
								ll.setCurrentTab(i);
								processTabClick((Activity) ll.getContext(), v,
										ll.getChildAt(sActiveTabIndex).getId());
								break;
							}
						}
					}
				}
			});

			v.setOnClickListener(new View.OnClickListener() {

				public void onClick(View v) {
					processTabClick((Activity) ll.getContext(), v, ll
							.getChildAt(sActiveTabIndex).getId());
				}
			});
		}
		return withtabs;
	}

	static void processTabClick(Activity a, View v, int current) {
		int id = v.getId();
		if (id == current) {
			return;
		}

		final TabWidget ll = (TabWidget) a.findViewById(R.id.buttonbar);

		activateTab(a, id);
		if (id != R.id.nowplayingtab) {
			ll.setCurrentTab((Integer) v.getTag());
			setIntPref(a, "activetab", id);
		}
	}

	static void activateTab(Activity a, int id) {
		Intent intent = new Intent(Intent.ACTION_PICK);
		switch (id) {
		case R.id.artisttab:
			intent.setDataAndType(Uri.EMPTY,
					"vnd.android.cursor.dir/artistalbum");
			break;
		case R.id.albumtab:
			intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
			break;
		case R.id.songtab:
			intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
			break;
		case R.id.playlisttab:
			intent.setDataAndType(Uri.EMPTY,
					MediaStore.Audio.Playlists.CONTENT_TYPE);
			break;
		case R.id.nowplayingtab:
			intent = new Intent(a, CalinPodcastMediaPlayer.class);
			a.startActivity(intent);
			// fall through and return
		default:
			return;
		}
		intent.putExtra("withtabs", true);
		intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
		a.startActivity(intent);
		a.finish();
		a.overridePendingTransition(0, 0);
	}

	static void activateTab(TabActivity a, int id) {
//		Intent intent = new Intent(Intent.ACTION_PICK);
//		switch (id) {
//		case R.id.artisttab:
//			intent.setDataAndType(Uri.EMPTY,
//					"vnd.android.cursor.dir/artistalbum");
//			break;
//		case R.id.albumtab:
//			intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
//			break;
//		case R.id.songtab:
//			intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
//			break;
//		case R.id.playlisttab:
//			intent.setDataAndType(Uri.EMPTY,
//					MediaStore.Audio.Playlists.CONTENT_TYPE);
//			break;
//		case R.id.nowplayingtab:
//			intent = new Intent(a, CalinPodcastMediaPlayer.class);
//			a.startActivity(intent);
//			// fall through and return
//		default:
//			return;
//		}
//		intent.putExtra("withtabs", true);
//		intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
//		a.startActivity(intent);
//		a.finish();
//		a.overridePendingTransition(0, 0);

		try {
			final TabHost tabHost = a.getTabHost();

			Bundle bundle = a.getIntent().getExtras();
			Intent intentPodcastList = new Intent(a, CalinPodcastList.class);
			if (bundle != null)
				intentPodcastList.putExtras(bundle);
			tabHost.addTab(tabHost.newTabSpec("local").setIndicator("list")
					.setContent(intentPodcastList));

			tabHost.addTab(tabHost.newTabSpec("top").setIndicator("Top list")
					.setContent(intentPodcastList));

			// This tab sets the intent flag so that it is recreated each time
			// the tab is clicked.
			tabHost.addTab(tabHost.newTabSpec("category")
					.setIndicator("category").setContent(intentPodcastList));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	static void updateNowPlaying(Activity a) {
		View nowPlayingView = a.findViewById(R.id.nowplaying);
		if (nowPlayingView == null) {
			return;
		}
		try {
			boolean withtabs = false;
			Intent intent = a.getIntent();
			if (intent != null) {
				withtabs = intent.getBooleanExtra("withtabs", false);
			}
			if (true && MusicUtils.sService != null
					&& MusicUtils.sService.getAudioId() != -1) {
				TextView title = (TextView) nowPlayingView
						.findViewById(R.id.title);
				TextView artist = (TextView) nowPlayingView
						.findViewById(R.id.artist);
				title.setText(MusicUtils.sService.getTrackName());
				String artistName = MusicUtils.sService.getArtistName();
				if (MediaStore.UNKNOWN_STRING.equals(artistName)) {
					artistName = a.getString(R.string.unknown_artist_name);
				}
				artist.setText(artistName);
				// mNowPlayingView.setOnFocusChangeListener(mFocuser);
				// mNowPlayingView.setOnClickListener(this);
				nowPlayingView.setVisibility(View.VISIBLE);
				nowPlayingView.setOnClickListener(new View.OnClickListener() {

					public void onClick(View v) {
						Context c = v.getContext();
						c.startActivity(new Intent(c,
								CalinPodcastMediaPlayer.class));
					}
				});
				return;
			}
		} catch (RemoteException ex) {
		}
		nowPlayingView.setVisibility(View.GONE);
	}

	static void setBackground(View v, Bitmap bm) {

		if (bm == null) {
			v.setBackgroundResource(0);
			return;
		}

		int vwidth = v.getWidth();
		int vheight = v.getHeight();
		int bwidth = bm.getWidth();
		int bheight = bm.getHeight();
		float scalex = (float) vwidth / bwidth;
		float scaley = (float) vheight / bheight;
		float scale = Math.max(scalex, scaley) * 1.3f;

		Bitmap.Config config = Bitmap.Config.ARGB_8888;
		Bitmap bg = Bitmap.createBitmap(vwidth, vheight, config);
		Canvas c = new Canvas(bg);
		Paint paint = new Paint();
		paint.setAntiAlias(true);
		paint.setFilterBitmap(true);
		ColorMatrix greymatrix = new ColorMatrix();
		greymatrix.setSaturation(0);
		ColorMatrix darkmatrix = new ColorMatrix();
		darkmatrix.setScale(.3f, .3f, .3f, 1.0f);
		greymatrix.postConcat(darkmatrix);
		ColorFilter filter = new ColorMatrixColorFilter(greymatrix);
		paint.setColorFilter(filter);
		Matrix matrix = new Matrix();
		matrix.setTranslate(-bwidth / 2, -bheight / 2); // move bitmap center to
														// origin
		matrix.postRotate(10);
		matrix.postScale(scale, scale);
		matrix.postTranslate(vwidth / 2, vheight / 2); // Move bitmap center to
														// view center
		c.drawBitmap(bm, matrix, paint);
		v.setBackgroundDrawable(new BitmapDrawable(bg));
	}

	static int getCardId(Context context) {
		ContentResolver res = context.getContentResolver();
		Cursor c = res.query(Uri.parse("content://media/external/fs_id"), null,
				null, null, null);
		int id = -1;
		if (c != null) {
			c.moveToFirst();
			id = c.getInt(0);
			c.close();
		}
		return id;
	}

	static class LogEntry {
		Object item;
		long time;

		LogEntry(Object o) {
			item = o;
			time = System.currentTimeMillis();
		}

		void dump(PrintWriter out) {
			sTime.set(time);
			out.print(sTime.toString() + " : ");
			if (item instanceof Exception) {
				((Exception) item).printStackTrace(out);
			} else {
				out.println(item);
			}
		}
	}

	private static LogEntry[] sMusicLog = new LogEntry[100];
	private static int sLogPtr = 0;
	private static Time sTime = new Time();

	static void debugLog(Object o) {

		sMusicLog[sLogPtr] = new LogEntry(o);
		sLogPtr++;
		if (sLogPtr >= sMusicLog.length) {
			sLogPtr = 0;
		}
	}

	static void debugDump(PrintWriter out) {
		for (int i = 0; i < sMusicLog.length; i++) {
			int idx = (sLogPtr + i);
			if (idx >= sMusicLog.length) {
				idx -= sMusicLog.length;
			}
			LogEntry entry = sMusicLog[idx];
			if (entry != null) {
				entry.dump(out);
			}
		}
	}
}
